/*
    Copyright 2006-2011 Patrik Jonsson, sunrise@familjenjonsson.org

    This file is part of Sunrise.

    Sunrise is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Sunrise is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sunrise.  If not, see <http://www.gnu.org/licenses/>.

*/

/// \file
/// Functions that have to work transparently in mono/poly cases.

// $Id$

#ifndef __mono_poly_abstract__
#define __mono_poly_abstract__

#include "blitz/array.h"
#include "threadlocal.h"
#include <limits>

namespace mcrx {
  /** condition_all is an abstraction of applying the all reduction to
      an array. For scalar bools, this is just the bool itself. */
  bool condition_all(bool b);
  /** condition_all is an abstraction of applying the all reduction to
      an array. */
  template <int N> bool condition_all(const blitz::Array<bool, N>& a) {
    return blitz::all(a);};
  /** condition_all is an abstraction of applying the all reduction to
      an array. */
  template <typename T> bool condition_all(const blitz::_bz_ArrayExpr<T> a) {
    return blitz::all(a);};

  /** This function is used when copy constructing templated types to
      ensure that if the type is a blitz array, the copy does not
      reference the other array.  */
  template<typename T> 
  T independent_copy (T rhs) {return rhs;};
  template<typename T, int N>
  blitz::Array<T, N> independent_copy (const blitz::Array<T, N>& rhs) {
    blitz::Array<T, N> a(rhs.copy()); return a;};

  /** This function is used when assigning templated types to ensure
      that if the type is a blitz array it is sized appropriately. */
  template<typename T> 
  void assign (T lhs, T rhs) {lhs = rhs;};

  /** Array assign function. Assigns the contents of an array to
      another, and sets the locking policy. */
  template<typename T, int N>
  void assign (blitz::Array<T, N>& lhs, const blitz::Array<T, N>& rhs, 
	       bool dolock = false) {
    if(!same_size(lhs, rhs)) {
      lhs.reference(rhs.copy());
    }
    else
      lhs = rhs;
    threadLocal_warn(lhs, !dolock);
  };

  /** Array expression assign function. Assigns an array expression to
      an array, and sets the locking policy. */
  template<typename T, int N, typename U>
  void assign (blitz::Array<T, N>& lhs, const blitz::_bz_ArrayExpr<U>& rhs,
	       bool dolock = false) {
    if(!same_size(lhs, rhs))
      resize_like(lhs, rhs);
    threadLocal_warn(lhs, !dolock);
    lhs=rhs;
  };

  /** Checks that the arguments have the same size.  Always true for
      scalars.  */
  template<typename T>
  bool same_size (T, T) {return true;};

  /** Checks that the blitz arrays have the same size. We need an
      explicit array version because for some reason it picks the
      scalar one and not the ETBase one when used on two arrays. */
  template<typename T, int N>
  bool same_size (blitz::Array<T, N>& lhs, const blitz::Array<T, N>& rhs) {
    return all(lhs.shape()==rhs.shape()); };

  /** Checks that the blitz expressions have the same size. */
  template<typename T1, typename T2>
  bool same_size (blitz::ETBase<T1>& lhs, const blitz::ETBase<T2>& rhs) {
    assert(blitz::asExpr<T1>::T_expr::rank_==blitz::asExpr<T2>::T_expr::rank_);
    for(int i=0; i<blitz::asExpr<T1>::T_expr::rank_; ++i)
      if (lhs.unwrap().ubound(i)-lhs.unwrap().lbound(i)!=
	  rhs.unwrap().ubound(i)-rhs.unwrap().lbound(i))
	return false;
    return true;
  };
  
  /** Resizes the left-hand side to have the same size as the right
      hand side. Does nothing for scalars.  */
  template< typename T>
  void resize_like (T lhs, T rhs) {};

  /** Resizes the left-hand side to have the same size as the right
      hand side. We need an explicit array version because for some
      reason it picks the scalar one and not the ETBase one when used
      on two arrays. */
  template<typename T, int N>
  void resize_like (blitz::Array<T, N>& lhs, const blitz::Array<T, N>& rhs) {
    lhs.resize(rhs.shape());
  }; 

  /** Resizes the left-hand side to have the same size as the right
      hand side. Version that works on a blitz expression */
  template<typename T, int N, typename T2>
  void resize_like (blitz::Array<T, N>& lhs, const blitz::ETBase<T2>& rhs) {
    blitz::TinyVector<int, blitz::asExpr<T2>::T_expr::rank_> shape;
    for(int i=0; i<blitz::asExpr<T2>::T_expr::rank_; ++i)
      shape[i]= rhs.unwrap().ubound(i)-rhs.unwrap().lbound(i)+1;
    lhs.resize(shape);
  }; 
  
  /** Returns an initialized version of the templated type, sized
      appropriately. */
  template<typename T> 
  T initialize (T rhs, T val) {return val;};

  template<typename T, int N> 
  blitz::TinyVector<T, N> initialize (blitz::TinyVector<T, N>, T val) {
    return val;};

  template<typename T, int N>
  blitz::Array<T, N> initialize (const blitz::Array<T, N>& rhs, T val, 
				 bool dolock = false) {
    blitz::Array<T, N> temp(rhs.shape()); 
    threadLocal_warn(temp, !dolock);
    temp=val; return temp;
  };

  /** This function is used when constructing templated types to make
      a thread-local array that's not using mutex locking to protect
      the reference count. For arrays, the locking behavior is
      determined by the memoryblock and not the array itself, it must
      necessarily make a copy of the data block. */
  template<typename T>
  T make_thread_local_copy(T t) {return t;};
  /** The Array version of make_thread_local_copy returns a copy of the
      array which is not mutex locked. */
  template<typename T, int N>
  blitz::Array<T, N> make_thread_local_copy (const blitz::Array<T, N>& rhs) {
    blitz::Array<T, N> ret(rhs.copy());
    threadLocal_warn(ret, true);
    return ret;
  };
  /** The blitz expression version of make_thread_local_copy returns
      an array copy of the expression which is not mutex locked. */
  template<typename T>
  blitz::Array<typename blitz::_bz_ArrayExpr<T>::T_numtype, 
	       blitz::_bz_ArrayExpr<T>::rank_>
  make_thread_local_copy(const blitz::_bz_ArrayExpr<T>& expr) {
    blitz::Array<typename blitz::_bz_ArrayExpr<T>::T_numtype, 
      blitz::_bz_ArrayExpr<T>::rank_> ret (expr);
    threadLocal_warn(ret, true);
    return ret;
  };

  template<typename T, bool b> struct ispod {};
  template<typename T> struct ispod<T, true> {typedef T T_pod;};

  /** In a number of places, we use the blitz max function to retrieve
      the largest element of an array.  To abstract this away, we want
      to define max of a POD type as the value itself, but to avoid
      matching to non-POD types we use SFINAE and the ispod class.  */
  template<typename T>
  typename ispod<T, std::numeric_limits<T>::is_specialized>::T_pod max (T t) {return t;};

  /** The blitz where function reduces to the ternary operator for scalars.  */
  template<typename T>
  T where (bool c, T a, T b) {return c?  a: b;};
}

#endif
